/* 
 * Classical | JavaScript Classical Inheritance
 *
 * Copyright (c) 2008 Tim Cameron Ryan
 * Released under the MIT/X License
 *
 */
 
//[TODO] explore constructor chain; properties of multiple inheritance; interfaces
//[TODO] 'protected' scope (scope as an attribute of properties?)
 
(function () {
	/*
	 * object property iterator
	 */
	 
	function iterate(obj, callback)
	{
		// evaluate all defined properties
		for (var prop in obj)
			if (Object.prototype.hasOwnProperty.call(obj, prop))
				callback(obj[prop], prop);
	
		// IE has dontEnum issues
		/*@cc_on
		var dontenums = 'constructor|toString|valueOf|toLocaleString|isPrototypeOf|propertyIsEnumerable|hasOwnProperty'.split('|');
		for (var prop; prop = dontenums.pop(); )
			if (Object.prototype.hasOwnProperty.call(obj, prop) && !Object.prototype.propertyIsEnumerable.call(obj, prop))
				callback(obj[prop], prop);
		@*/
	}
	
	/*
	 * Singleton factory
	 */
	 
	function SingletonFactory(generator)
	{
		var cache;
		return function () {
			return cache || (cache = generator());
		}
	}
	
	/*
	 * simple unique ID
	 */
	 
	var uniqID = (function () {
		var id = 0;
		return function () { return id++; }
	})();
	
	/*
	 * Class modeling
	 */
	
	function ScopeDefinition()
	{
		// scope definitions
		this.methods = {};
		this.properties = {};
		this.getters = {};
		this.setters = {};

		this.map = function (callbacks) {
			for (var type in callbacks)
				iterate(this[type], callbacks[type]);
		}
	}

	function Implementation(classID, inherit)
	{
		this.classID = classID;
		this.scopes = {
			'public': new ScopeDefinition(),
			'private': new ScopeDefinition()
		};
		this.inherit = inherit;
		this.constructor = null;
		this.cast = null;
	}
	
	function Property(value)
	{
		this.generate = function (obj) {
			return value;
		}
	}
	
	function DynamicProperty(func)
	{
		this.generate = function (obj) {
			return func.call(null, obj);
		}
	}
	
	/*
	 * Scoped methods
	 */
	 
//[TODO] evaluation techniques need to be sped up the most
//[TODO] also, scoped methods have way too much access to Classical variables...
	
	function ScopedMethodGenerator()
	{
		var cache = {}, code = '';
		
		this.generate = function (func)
		{
			// add evaluator
			var id = 'method' + uniqID();
			code += 'cache["' + id + '"] = (function (obj) { return (function () { with (obj.scopes["private"]) with (obj.scopes["public"]) { return (' + func + ').apply(obj.scopes["public"], arguments); } }); });';
	
			// create method
			return new DynamicProperty(function (obj) {
				return cache[id](obj);
			});
		}
		
		this.evaluate = function ()
		{
			// evaluate all methods
			eval(code);
		}
	}
	
	/*
	 * Object factory
	 */
	
	function ObjectFactory(implementation)
	{	
		// create scope implementations from definiton
		var scopes = {
			'public': new ScopeGenerator(implementation.scopes['public']),
			'private': new ScopeGenerator(implementation.scopes['private'])
		};
		
		// object generation
		this.generate = function ()
		{	
			return new ClassObject(implementation, scopes);
		}
	}
	
	/*
	 * Class object
	 */

	function ClassObject(implementation, scopes)
	{
		// create parent object
		if (implementation.inherit)
			this.parent = implementation.inherit.objectGenerator.generate();
			
		// generate scope objects
		this.scopes = {'public': {}, 'private': {}};
		this.scopes['public'] = scopes['public'].generate(this);
		this.scopes['private'] = scopes['private'].generate(this);
		
		// generate constructor
		this.constructor = implementation.constructor ? implementation.constructor.generate(this) :
		    this.parent ? this.parent.constructor :
		    function () { };
	}
	
	/*
	 * scope object generator
	 */

//[TODO] better object names
	function ScopeGenerator(definition)
	{
		// initialize
		var objectGenerator = new ScopeObject(definition);
		
		this.generate = function (obj) {
			// call the factory object
			var scope = objectGenerator.generate();
			// augment its properties
			var propPrefixes = {getters: 'get_', setters: 'set_', methods: '', properties: ''};
			for (var type in propPrefixes)
				iterate(definition[type], function (value, prop) {
					scope[propPrefixes[type] + prop] = value.generate(obj);
				});
			return scope;
		}
	}
	
	// override
	var ScopeObject;
	
	/*
	 * defineGetter/Setter scope object
	 */
	
	function DefineGetterSetterScopeObject(definition)
	{
		// generate a new factory
		var Factory = function () { }
		// generate from scope
		definition.map({
			'getters': function (value, prop) {
				Factory.prototype.__defineGetter__(prop, function () { return this['get_' + prop](); });
			},
			'setters': function (value, prop) {
				Factory.prototype.__defineSetter__(prop, function (val) { this['set_' + prop](val); });
			}
		});
		
		this.generate = function () {
			return new Factory();
		}
	}
	
	/*
	 * vbscript scope object
	 */
	
	function VBScriptScopeObject(definition)
	{
		// code to be evaluated
		var code = '', className = 'class' + uniqID();
		// generate code from definition
		definition.map({
			'properties': function (value, prop) {
				code += 'Public ' + prop + '\n';
			},
			'methods': function (value, prop) {
				code += 'Public ' + prop + '\n';
			},
			'getters': function (value, prop) {
				code += 'Public get_' + prop + '\n\
Public Property Get ' + prop + '\n\
	Dim proxy\n\
	Set proxy = new TypeProxy\n\
	proxy.load(get_' + prop + '(me))\n\
	If IsObject(proxy.value) Then\n\
		Set ' + prop + ' = proxy.value\n\
	Else\n\
		' + prop + ' = proxy.value\n\
	End If\n\
End Property\n';
			},
			'setters': function (value, prop) {
				code += 'Public set_' + prop + '\n\
Public Property Let ' + prop + '(val)\n\
	Call set_' + prop + '(val)\n\
End Property\n\
Public Property Set ' + prop + '(val)\n\
	Call set_' + prop + '(val)\n\
End Property\n'
			}
		});
		
		// get sandbox
		var sandbox = VBScriptScopeObject.getSandbox();
		// evaluate class
//[TODO] there has to be a way to detect and notify about bad property names
		sandbox.execute('Class ' + className + '\n' + code + '\nEnd Class');

		this.generate = function () {
			return sandbox.evaluate('New ' + className);
		}
	}

	// cache sandbox generation for speed
	VBScriptScopeObject.getSandbox = new SingletonFactory(function () {
		return new VBScriptSandbox();
	});
	
	/*
	 * VBScript sandbox
	 */
	 
	function VBScriptSandbox()
	{		
		// create an iframe sandbox (in head, as not to pollute the body)
		var frame = document.createElement('iframe');
		frame.style.display = 'none';
		document.getElementsByTagName('head')[0].appendChild(frame);
		
		// get variables
		this.global = frame.contentWindow;
		// write document
		this.global.document.write(
'<html><head><title>VBScript Sandbox</title><script type="text/vbscript">\
Class TypeProxy\n\
	Public value\n\
	Public Function load(val)\n\
		If IsObject(val) Then\n\
			Set value = val\n\
		Else\n\
			value = val\n\
		End If\n\
	End Function\n\
End Class\n\
Function execute(code)\n\
	ExecuteGlobal(code)\n\
End Function\n\
Function evaluate(code)\n\
	Dim proxy\n\
	Set proxy = new TypeProxy\n\
	proxy.load(Eval(code))\n\
	If IsObject(proxy.value) Then\n\
		Set evaluate = proxy.value\n\
	Else\n\
		evaluate = proxy.value\n\
	End If\n\
End Function\n\
</script></head><body></body></html>');
		this.global.document.close();
		
		// evaluation functions
		this.evaluate = function (code) { return this.global.evaluate(code); }
		this.execute = function (code) { this.global.execute(code); }
	}

	/*
	 * Class api
	 */
	
	// determine a getter/setter profile
	if (Object.prototype.__defineGetter__ && Object.prototype.__defineSetter__)
		ScopeObject = DefineGetterSetterScopeObject;
	else if (/*@cc_on!@*/0)
		ScopeObject = VBScriptScopeObject;
	else 
		throw new Error('The current browser is unable to support Classical.');
	
	// private classes array
	var classes = {};

	// class constructor
	window.Class = function (classID, properties, inherit)
	{
		// check that no class of this name exists
		if (Object.prototype.hasOwnProperty.call(classes, classID))
			throw new Error('A class with id "' + classID + '" has already been defined.');
		
		// placeholder for generated factory
		var ClassFactory;
		// create a new implementation
		var implementation = new Implementation(classID, inherit);
		// create a scoped method generator
		var scopedMethodGenerator = new ScopedMethodGenerator();
		
		// add constructor and castor
		implementation.constructor = properties['[constructor]'] && scopedMethodGenerator.generate(properties['[constructor]']);
		implementation.cast = properties['[cast]'] || (inherit && inherit.implementation.cast) || function () { throw new Error('Object cannot be cast'); };
		
		// add inheritance
		if (inherit)
		{
			// inherit properties
			var scope = implementation.scopes['public'];
			inherit.implementation.scopes['public'].map({
				'properties': function (value, prop) {
					scope.getters[prop] = new DynamicProperty(function (obj) { return (function () { return obj.parent.scopes['public'][prop]; }); });
					scope.setters[prop] = new DynamicProperty(function (obj) { return (function (val) { obj.parent.scopes['public'][prop] = val; }); });
				},
				'methods': function (method, prop) {
					scope.methods[prop] = new DynamicProperty(function (obj) { return obj.parent.scopes['public'][prop]; });
				},
				'getters': function (method, prop) {
					scope.getters[prop] = new DynamicProperty(function (obj) { return (function () { return obj.parent.scopes['public'][prop]; }); });
				},
				'setters': function (method, prop) {
					scope.setters[prop] = new DynamicProperty(function (obj) { return (function (val) { obj.parent.scopes['public'][prop] = val; }); });
				}
			});
			
			// add 'uber' keyword to private scope
			implementation.scopes['private'].getters.uber = new DynamicProperty(function (obj) {
				return (function () {
					return obj.parent.scopes['public'];
				});
			});
			// and uberConstructor
			implementation.scopes['private'].getters.uberConstructor = new DynamicProperty(function (obj) {
				return (function () {
					return obj.parent.constructor;
				});
			});
		}
	
		// add properties
		iterate(properties, function (value, prop) {
			// validate property name
			if (!prop.match(/^(public |private )?([gs]et )?[a-zA-Z][\w\$_]*$/))
				return;
			
			// get attributes
			var visibility = prop.match(/^private /) ? 'private' : 'public';
			var type = prop.match(/^(private |public )?get /) ? 'getters' :
			    prop.match(/^(private |public )?set /) ? 'setters' :
			    (typeof properties[prop]) == 'function' ? 'methods' :
			    'properties';
			// add property
			implementation.scopes[visibility][type][prop.replace(/^(public |private )?([gs]et )?/, '')] =
			    type == 'properties' ? new Property(value) : scopedMethodGenerator.generate(value);
		});
		
		// initialize object-specific properties
		implementation.scopes['public'].getters.constructor = new Property(function () { return ClassFactory; });
//[TODO] this fails on IE
		implementation.scopes['public'].methods.toString =
		    implementation.scopes['public'].methods.valueOf =
		    new Property(function () { return '[object ' + classID + ']'; });
	
		// evaluate scoped methods
		scopedMethodGenerator.evaluate();
		// create static generator
		var objectGenerator = new ObjectFactory(implementation);

		// create class factory
		ClassFactory = function ClassFactory()
		{
			// check if this be a cast
			if (!(this instanceof ClassFactory))
				return implementation.cast.apply(ClassFactory, arguments);

			// construct object
			var obj = objectGenerator.generate();
			obj.constructor.apply(obj, arguments);
			return obj.scopes['public'];
		}	
		// add class information
		iterate({
			objectGenerator: objectGenerator,
			classID: classID,
			implementation: implementation,
			toString: function () { return '[class ' + classID + ']'; },
			type: inherit ? [classID].concat(inherit.type) : [classID],
			hasInstance: function (obj)
			{
				if (!obj.constructor || !obj.constructor.type)
					return false;
				for (var i = 0; i < obj.constructor.type.length; i++)
					if (obj.constructor.type[i] == ClassFactory.classID)
						return true;
				return false;
			}
		}, function (value, prop) { ClassFactory[prop] = value; });
		
		// globalize class
		return window[classID] = classes[classID] = ClassFactory;
	}
})();